package zone

import (
	"context"
	"time"

	"gddgame.cc/galaxy/utils"

	"github.com/cayleygraph/cayley/graph"
	"github.com/cayleygraph/cayley/quad"
)

// 负责进行定时调度
// 节点事件触发=>节点变更状态=>遍历节点更新状态=>影响势力
// 事件自定义：参战，获胜/失败，建设
// 触发节点事件：传入节点状态，事件，可能改变节点状态，影响参战势力
// 触发关联节点更新（递归，广度遍历）：传入节点状态，更新节点属性
// 节点更新完毕，执行势力更新
// 注：势力可以根据情况调用调度器触发节点事件

type Strategy interface {
	EmitNode(node *Node, event interface{}, params []interface{})
	UpdateNode(node *Node)
	UpdatePower(power *Power)
}

type IntervalStrategy interface {
	Strategy
	Interval(layout *Layout) time.Duration
}

type Dispatch interface {
	Listen(layout *Layout, context context.Context) error
	Emit(nodeId string, event interface{}, params []interface{})
}

type dispatcher struct {
	context context.Context
}

func newDispatcher() *dispatcher {
	return &dispatcher{
		context: context.Background(),
	}
}

func (d *dispatcher) Close() {
	d.context.Done()
	context.WithCancel(d.context)
}

func (d *dispatcher) Attach(dispatch Dispatch, layout *Layout) error {
	return dispatch.Listen(layout, d.context)
}

type intervalDispatch struct {
	layout    *Layout
	strategy  IntervalStrategy
	change    chan interface{}
	changeSet *utils.Set
}

func NewIntervalDispatch(strategy IntervalStrategy) Dispatch {
	dd := &intervalDispatch{
		change:    make(chan interface{}, 10),
		changeSet: utils.NewSet(),
	}
	dd.strategy = strategy
	return dd
}

func (d *intervalDispatch) Listen(layout *Layout, context context.Context) error {
	d.layout = layout
	go func() {
		after := time.After(d.strategy.Interval(layout))
		for {
			select {
			case <-after:
				// 按间隔时间调度
				l := d.changeSet.List()
				d.changeSet.Clear()
				go func() {
					pl := d.updateNode(l)
					d.updatePower(pl)
					after = time.After(d.strategy.Interval(layout))
				}()
			case node := <-d.change:
				d.changeSet.Add(node)
			case <-context.Done():
				return
			}
		}
	}()
	return nil
}

func (d *intervalDispatch) updateNode(list []interface{}) []interface{} {
	if len(list) == 0 {
		return nil
	}
	powerSet := utils.NewSet()

	_ = d.layout.Transaction(func(tx *graph.Transaction) error {
		qw := graph.NewTxWriter(tx, graph.Add)
		for _, nodeId := range list {
			node := d.layout.NodeIRI(nodeId.(quad.IRI))
			d.strategy.UpdateNode(node)

			if node.changeStatus {
				list := node.Forward()
				for _, n := range list {
					d.change <- n.ID
				}
			}
			_, _ = d.layout.UpdateNode(qw, node)
			if node.changeRelation {
				for _, power := range node.changePowers {
					powerSet.Add(power.ID)
				}
			}
		}
		return nil
	})
	return powerSet.List()
}

func (d *intervalDispatch) updatePower(list []interface{}) {
	if len(list) == 0 {
		return
	}
	_ = d.layout.Transaction(func(tx *graph.Transaction) error {
		qw := graph.NewTxWriter(tx, graph.Add)
		for _, nodeId := range list {
			power := d.layout.PowerIRI(nodeId.(quad.IRI))
			d.strategy.UpdatePower(power)
			_, _ = d.layout.UpdatePower(qw, power)
		}
		return nil
	})

}

func (d *intervalDispatch) Emit(nodeId string, event interface{}, params []interface{}) {
	node := d.layout.Node(nodeId)
	d.strategy.EmitNode(node, event, params)
	if node.changeAttach {
		d.change <- node.ID
	}
}
